from datetime import datetime
from django.db import transaction
from django.core.exceptions import ObjectDoesNotExist
from django.utils import timezone
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework.exceptions import ValidationError
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework_simplejwt.authentication import JWTAuthentication

from .tasks import start_exam, end_exam
from .models import Exam, ExamDepart
from .serializers import ExamSerializer, ExamListSerializer, UnExamUserSerializer
from ..DepartManagement.models import Department
from ..PaperManagement.models import Paper
from ..ResultManagement.models import Results, ResultQuestionSolution
from ..PaperManagement.serializers import PaperListSerializer
from ..UserManagement.models import User
from ..utils.pageNumberPagination import CustomPageNumberPagination
from ..utils.response_utils import api_response, ResponseCode


class ExamListView(APIView):
    """
    获取考试列表接口
    """
    authentication_classes = [JWTAuthentication]
    permission_classes = [IsAuthenticated]
    pagination_class = CustomPageNumberPagination

    @swagger_auto_schema(
        operation_description="查询所有考试列表，支持按考试名称进行筛选",
        manual_parameters=[
            openapi.Parameter('title', openapi.IN_QUERY, description="考试名称", type=openapi.TYPE_STRING),
            openapi.Parameter('status', openapi.IN_QUERY, description="考试状态", type=openapi.TYPE_INTEGER,
                              enum=[0, 1, 2, 3]),
        ],
        responses={
            200: ExamListSerializer(many=True),
            400: "请求参数错误",
            500: "服务器内部错误"
        },
        tags=['考试管理']
    )
    def get(self, request):
        try:
            title = request.query_params.get("title")
            status = request.query_params.get("status")
            page = request.query_params.get("pageNo", 1)
            page_size = request.query_params.get("pageSize", 10)

            exams = Exam.objects.filter(parent=None).order_by("title")

            if title:
                exams = exams.filter(title__icontains=title)

            if status:
                exams = exams.filter(status=status)

            if not exams:
                return api_response(ResponseCode.SUCCESS, "未找到符合条件的考试", [])

            paginator = CustomPageNumberPagination()
            paginator.page_size = int(page_size)  # 每页的条数
            paginator.page_query_param = 'pageNo'  # 页码的参数名
            paginator.page_size_query_param = 'page_size'  # 每页数据条数的参数名

            result_page = paginator.paginate_queryset(exams, request)
            serializer = ExamListSerializer(result_page, many=True)
            paginated_response = paginator.get_paginated_response(serializer.data)

            return api_response(ResponseCode.SUCCESS, '考试列表获取成功', paginated_response.data)

        except ValidationError as e:
            return api_response(ResponseCode.BAD_REQUEST, f"请求参数错误: {str(e)}")
        except Exception as e:
            return api_response(ResponseCode.INTERNAL_SERVER_ERROR, f"服务器内部错误: {str(e)}")


class ExamUpdateView(APIView):
    """
    修改考试接口
    """
    authentication_classes = [JWTAuthentication]
    permission_classes = [IsAuthenticated]

    @swagger_auto_schema(
        operation_description="修改考试的状态、时间和试卷，并设置定时任务",
        request_body=openapi.Schema(
            type=openapi.TYPE_OBJECT,
            required=['id', 'title', 'start_time', 'end_time'],
            properties={
                'id': openapi.Schema(type=openapi.TYPE_STRING, description="考试ID"),
                'title': openapi.Schema(type=openapi.TYPE_STRING, description="考试名称"),
                'start_time': openapi.Schema(type=openapi.TYPE_STRING, description="考试开始时间"),
                'end_time': openapi.Schema(type=openapi.TYPE_STRING, description="考试结束时间"),
                're_answer': openapi.Schema(type=openapi.TYPE_INTEGER, description='是否允许重做'),
                'show_answer': openapi.Schema(type=openapi.TYPE_INTEGER, description='是否立即显示答案'),
                'departments': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING),
                                              description="参考部门ID列表"),
                'users': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING),
                                        description="参考用户ID列表"),
            },
        ),
        responses={
            200: ExamSerializer(),
            400: "请求参数错误",
            404: "未找到考试",
            500: "服务器内部错误"
        },
        tags=['考试管理']
    )
    def put(self, request):
        try:
            exam_id = request.data.get('id')
            title = request.data.get('title', None)
            start_time = request.data.get('start_time', None)
            end_time = request.data.get('end_time', None)
            re_answer = request.data.get('re_answer', None)
            reference_ids = request.data.get('departments', None)
            user_ids = request.data.get('users', None)
            show_answer = request.data.get('show_answer')

            exam = Exam.objects.get(id=exam_id)
            paper = Paper.objects.get(id=exam.paper.id)

            if exam.status == 1:
                return api_response(ResponseCode.BAD_REQUEST, "考试进行中，不允许修改")
            if title is not None:
                exam.title = title
            if show_answer == 0:
                show_answer = False
            else:
                show_answer = True
            if re_answer == 0:
                re_answer = False
            else:
                re_answer = True
            if reference_ids is None and user_ids is None:
                exam.status = 3

            # 获取当前时区（Django配置的时区）
            current_tz = timezone.get_current_timezone()
            # 解析字符串为 datetime 对象
            start_time = datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S')
            end_target = start_time + timezone.timedelta(minutes=paper.exam_duration)
            end_time = datetime.strptime(end_time, '%Y-%m-%d %H:%M:%S')
            # 将 naive datetime 转换为 aware datetime（使用当前时区）
            aware_start_time = timezone.make_aware(start_time, timezone=current_tz)
            aware_end_time = timezone.make_aware(end_time, timezone=current_tz)
            if end_target != end_time:
                return api_response(ResponseCode.BAD_REQUEST, '考试时间长度与结束时间不匹配')

            exam.start_time = aware_start_time
            exam.end_time = aware_end_time
            exam.paper = paper
            exam.re_answer = re_answer
            exam.show_answer = show_answer

            # 保存修改后的考试
            exam.save()

            try:
                # 开启事务
                with transaction.atomic():
                    # 删除试卷中的所有试题
                    ExamDepart.objects.filter(exam=exam).delete()

                    # 添加部门到考试
                    for reference_id in reference_ids:
                        try:
                            department = Department.objects.get(id=reference_id)
                        except ObjectDoesNotExist:
                            raise ValueError(f'部门不存在')

                        if ExamDepart.objects.filter(exam=exam, depart=department).first():
                            raise ValueError(f'部门已添加在考试{exam.title}中')

                        ExamDepart.objects.create(exam=exam, depart=department)

                    # 添加用户到考试
                    for user_id in user_ids:
                        try:
                            user = User.objects.get(id=user_id)
                        except ObjectDoesNotExist:
                            raise ValueError(f'用户不存在')

                        if ExamDepart.objects.filter(exam=exam, user=user).first():
                            raise ValueError(f'用户已添加在考试{exam.title}中')

                        ExamDepart.objects.create(exam=exam, user=user)
            except Exception as e:
                exam.status = 3
                exam.save()
                # 如果发生异常，回滚事务
                transaction.rollback()
                return api_response(ResponseCode.BAD_REQUEST, f"添加部门到考试失败: {str(e)}")

            # 根据新的考试时间设置定时任务
            if aware_start_time > timezone.now() and exam.status == 0:
                # 如果考试的开始时间是未来的时间且考试状态为 "未开始"，则设置启动考试任务
                start_exam.apply_async((exam.id,), eta=aware_start_time)
                aware_end_time -= timezone.timedelta(seconds=1)
                end_exam.apply_async((exam.id,), eta=aware_end_time)
            elif aware_end_time > timezone.now() and exam.status == 1:
                # 如果考试的结束时间是未来的时间且考试状态为 "进行中"，则设置结束考试任务
                aware_end_time -= timezone.timedelta(seconds=1)
                end_exam.apply_async((exam.id,), eta=aware_end_time)
            else:
                api_response(ResponseCode.BAD_REQUEST, "开始和结束时间必须大于当前时间")

            serializer = ExamSerializer(exam)

            return api_response(ResponseCode.SUCCESS, "修改成功", serializer.data)

        except ObjectDoesNotExist:
            return api_response(ResponseCode.NOT_FOUND, "未找到考试")
        except Exception as e:
            return api_response(ResponseCode.INTERNAL_SERVER_ERROR, "系统内部错误", str(e))


class ResetExam(APIView):
    """
    重置考试状态
    """
    authentication_classes = [JWTAuthentication]
    permission_classes = [IsAuthenticated]

    @swagger_auto_schema(
        operation_description="重置考试的状态为未开考",
        request_body=openapi.Schema(
            type=openapi.TYPE_OBJECT,
            required=['id', 'start_time', 'end_time'],
            properties={
                'id': openapi.Schema(type=openapi.TYPE_STRING, description="考试ID"),
                'start_time': openapi.Schema(type=openapi.TYPE_STRING, description="考试开始时间"),
                'end_time': openapi.Schema(type=openapi.TYPE_STRING, description="考试结束时间"),
                'users': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING),
                                        description="参考用户ID列表"),
            },
        ),
        responses={
            200: ExamSerializer(),
            400: "请求参数错误",
            404: "未找到考试",
            500: "服务器内部错误"
        },
        tags=['考试管理']
    )
    def put(self, request):
        try:
            exam_id = request.data.get('id')
            start_time = request.data.get('start_time', None)
            end_time = request.data.get('end_time', None)
            users = request.data.get('users', [])
            exam = Exam.objects.get(id=exam_id)
            paper = Paper.objects.get(id=exam.paper.id)

            # 获取当前时区（Django配置的时区）
            current_tz = timezone.get_current_timezone()
            # 解析字符串为 datetime 对象
            start_time = datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S')
            end_target = start_time + timezone.timedelta(minutes=paper.exam_duration)
            end_time = datetime.strptime(end_time, '%Y-%m-%d %H:%M:%S')
            # 将 naive datetime 转换为 aware datetime（使用当前时区）
            aware_start_time = timezone.make_aware(start_time, timezone=current_tz)
            aware_end_time = timezone.make_aware(end_time, timezone=current_tz)
            if end_target != end_time:
                return api_response(ResponseCode.BAD_REQUEST, '考试时间长度与结束时间不匹配')

            children = Exam.objects.create(
                parent=exam,
                start_time=aware_start_time,
                end_time=aware_end_time,
                paper=paper,
                title=exam.title,
                re_answer=exam.re_answer,
                show_answer=exam.show_answer,
            )
            try:
                for user_id in users:
                    user_obj = User.objects.filter(id=user_id).first()
                    if not user_obj:
                        return api_response(ResponseCode.NOT_FOUND, f'用户{user_obj.name}不存在')
                    ExamDepart.objects.create(exam=children, user=user_obj)
            except Exception as e:
                exam.status = 3
                children.delete()
                exam.save()
                return api_response(ResponseCode.BAD_REQUEST, f"添加部门到考试失败: {str(e)}")

            # 根据新的考试时间设置定时任务
            if aware_start_time > timezone.now() and children.status == 0:
                # 如果考试的开始时间是未来的时间且考试状态为 "未开始"，则设置启动考试任务
                start_exam.apply_async((children.id,), eta=aware_start_time)
                aware_end_time -= timezone.timedelta(seconds=1)
                end_exam.apply_async((children.id,), eta=aware_end_time)
            else:
                api_response(ResponseCode.BAD_REQUEST, "开始和结束时间必须大于当前时间")

            serializer = ExamSerializer(children)

            return api_response(ResponseCode.SUCCESS, "修改成功", serializer.data)

        except ObjectDoesNotExist:
            return api_response(ResponseCode.NOT_FOUND, "未找到考试")
        except Exception as e:
            return api_response(ResponseCode.INTERNAL_SERVER_ERROR, "系统内部错误", str(e))


class GetPaperList(APIView):
    """
    试卷下拉框
    """
    authentication_classes = [JWTAuthentication]
    permission_classes = [IsAuthenticated]

    @swagger_auto_schema(
        operation_description="试卷下拉框接口",
        responses={
            200: PaperListSerializer(many=True),
            400: "请求参数错误",
            500: "服务器内部错误"
        },
        tags=["考试管理"]
    )
    def get(self, request):
        try:
            papers = Paper.objects.filter(status=True)

            if not papers:
                return api_response(ResponseCode.SUCCESS, "未找到符合条件的试卷", [])

            serializer = PaperListSerializer(papers, many=True)

            return api_response(ResponseCode.SUCCESS, "获取成功", serializer.data)

        except ValidationError as e:
            return api_response(ResponseCode.BAD_REQUEST, f"请求参数错误: {str(e)}")
        except Exception as e:
            return api_response(ResponseCode.INTERNAL_SERVER_ERROR, f"服务器内部错误: {str(e)}")


class CreateExam(APIView):
    """
    创建考试
    """
    authentication_classes = [JWTAuthentication]
    permission_classes = [IsAuthenticated]

    @swagger_auto_schema(
        operation_description="创建考试接口",
        request_body=openapi.Schema(
            type=openapi.TYPE_OBJECT,
            required=['paper_id', 'title', 'start_time', 'end_time'],
            properties={
                'paper_id': openapi.Schema(type=openapi.TYPE_STRING, description='试卷ID'),
                'title': openapi.Schema(type=openapi.TYPE_STRING, description='考试名称'),
                'start_time': openapi.Schema(type=openapi.TYPE_STRING, description='考试开始时间'),
                'end_time': openapi.Schema(type=openapi.TYPE_STRING, description='考试结束时间'),
                're_answer': openapi.Schema(type=openapi.TYPE_INTEGER, description='是否允许重做'),
                'show_answer': openapi.Schema(type=openapi.TYPE_INTEGER, description='是否立即显示答案'),
                'departments': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING),
                                              description="参考部门ID列表"),
            },
        ),
        responses={
            200: openapi.Response(
                '创建成功！',
                openapi.Schema(
                    type=openapi.TYPE_OBJECT,
                    properties={
                        'title': openapi.Schema(type=openapi.TYPE_STRING, description='考试名称'),
                    }
                )
            ),
            400: openapi.Response('请求参数不合法'),
            409: openapi.Response('考试已存在'),
            404: openapi.Response('试卷不存在'),
        },
        tags=["考试管理"]
    )
    def post(self, request):
        paper_id = request.data.get('paper_id')
        title = request.data.get('title')
        start_time_str = request.data.get('start_time')
        end_time_str = request.data.get('end_time')  # "start_time": "2023-10-05 14:30:00",
        re_answer = request.data.get('re_answer')
        depart_ids = request.data.get('departments', [])
        show_answer = request.data.get('show_answer')

        if show_answer == 0:
            show_answer = False
        else:
            show_answer = True

        if re_answer == 0:
            re_answer = False
        else:
            re_answer = True
        if not paper_id or not title or not start_time_str:
            return api_response(ResponseCode.BAD_REQUEST, '请求参数不合法')

        exam = Exam.objects.filter(id=paper_id, status__in=[0, 1])
        if exam:
            return api_response(ResponseCode.CONFLICT, '考试已存在')

        paper = Paper.objects.filter(id=paper_id).first()
        if not paper:
            return api_response(ResponseCode.NOT_FOUND, '试卷不存在')

        # 获取当前时区（Django配置的时区）
        current_tz = timezone.get_current_timezone()
        # 解析字符串为 datetime 对象
        start_time = datetime.strptime(start_time_str, '%Y-%m-%d %H:%M:%S')
        end_target = start_time + timezone.timedelta(minutes=paper.exam_duration)
        end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
        # 将 naive datetime 转换为 aware datetime（使用当前时区）
        aware_start_time = timezone.make_aware(start_time, timezone=current_tz)
        aware_end_time = timezone.make_aware(end_time, timezone=current_tz)
        if end_target != end_time:
            return api_response(ResponseCode.BAD_REQUEST, '考试时间长度与结束时间不匹配')

        exam = Exam.objects.create(
            paper=paper,
            title=title,
            start_time=aware_start_time,
            end_time=aware_end_time,
            re_answer=re_answer,
            show_answer=show_answer,
        )
        try:
            # 添加部门到考试
            for depart_id in depart_ids:
                department = Department.objects.filter(id=depart_id).first()

                if not department:
                    return api_response(ResponseCode.NOT_FOUND, f'部门{depart_id}不存在')

                if ExamDepart.objects.filter(exam=exam, depart=department).first():
                    return api_response(ResponseCode.CONFLICT, f'部门{depart_id}已添加在考试{exam.id}中')

                ExamDepart.objects.create(exam=exam, depart=department)

            # 根据新的考试时间设置定时任务
            if aware_start_time > timezone.now() and exam.status == 0:
                # 如果考试的开始时间是未来的时间且考试状态为 "未开始"，则设置启动考试任务
                start_exam.apply_async((exam.id,), eta=aware_start_time)
                aware_end_time -= timezone.timedelta(seconds=1)
                end_exam.apply_async((exam.id,), eta=aware_end_time)
            else:
                api_response(ResponseCode.BAD_REQUEST, "开始和结束时间必须大于当前时间")

            return api_response(ResponseCode.SUCCESS, '创建成功！', {'title': exam.title})
        except Exception as e:
            exam.delete()
            return api_response(ResponseCode.INTERNAL_SERVER_ERROR, f"服务器内部错误: {str(e)}")


class DeleteExam(APIView):
    """
    删除考试
    """
    authentication_classes = [JWTAuthentication]
    permission_classes = [IsAuthenticated]

    @swagger_auto_schema(
        operation_description="删除考试接口",
        request_body=openapi.Schema(
            type=openapi.TYPE_OBJECT,
            properties={
                'ids': openapi.Schema(
                    type=openapi.TYPE_ARRAY,
                    items=openapi.Schema(type=openapi.TYPE_STRING),
                    description='考试ID列表，删除多个考试'
                ),
            },
        ),
        responses={
            200: "删除成功",
            400: "未找到该考试"
        },
        tags=['考试管理']

    )
    def delete(self, request):
        try:
            # 获取请求中的考试 ID 列表
            exam_ids = request.data.get('ids', [])

            if not exam_ids:
                return api_response(ResponseCode.BAD_REQUEST, "请求数据无效，必须提供试卷ID列表")
            index = 0
            for exam_id in exam_ids:
                # 找到要删除的考试
                paper_to_delete = Exam.objects.filter(id=exam_id).first()
                if paper_to_delete is None:
                    continue
                paper_to_delete.delete()
                index += 1
            if len(exam_ids) == index:
                return api_response(ResponseCode.SUCCESS, f"删除成功，共删除{index}条数据")
            else:
                return api_response(ResponseCode.SUCCESS, f"已删除{index}条数据，有{len(exam_ids) - index}条数据不存在")

        except Exception as e:
            return api_response(ResponseCode.INTERNAL_SERVER_ERROR, f"服务器内部错误: {str(e)}")


class ExamDetailView(APIView):
    """
    根据id查询考试
    """
    authentication_classes = [JWTAuthentication]
    permission_classes = [IsAuthenticated]

    @swagger_auto_schema(
        operation_description="根据id查询考试",
        manual_parameters=[
            openapi.Parameter('id', openapi.IN_QUERY, description="考试id", type=openapi.TYPE_STRING),
        ],
        responses={
            200: "查询成功",
            400: "未找到该考试"
        },
        tags=['考试管理']
    )
    def get(self, request):
        try:
            exam_id = request.query_params.get("id")

            if not exam_id:
                return api_response(ResponseCode.BAD_REQUEST, "请求数据无效，必须提供试卷ID")

            paper = Exam.objects.get(id=exam_id)

            serializer = ExamSerializer(paper)
            return api_response(ResponseCode.SUCCESS, "查询成功", serializer.data)

        except ObjectDoesNotExist:
            return api_response(ResponseCode.NOT_FOUND, "未找到考试")
        except Exception as e:
            return api_response(ResponseCode.INTERNAL_SERVER_ERROR, f"服务器内部错误: {str(e)}")



class UnExamUser(APIView):
    """
    考试中查看未提交答题卡的考试人员
    """
    authentication_classes = [JWTAuthentication]
    permission_classes = [AllowAny]

    @swagger_auto_schema(
        operation_description="考试中查看未提交答题卡的考试人员",
        manual_parameters=[
            openapi.Parameter('exam_id', openapi.IN_QUERY, description="考试ID", type=openapi.TYPE_STRING),
        ],
        responses={
            200: UnExamUserSerializer(many=True),
            400: "请求参数错误",
            500: "服务器内部错误"
        },
        tags=['考试管理']
    )
    def get(self, request):
        try:
            exam_id = request.query_params.get("exam_id")
            exam_profile = Exam.objects.filter(id=exam_id).first()
            if not exam_profile:
                return api_response(ResponseCode.NOT_FOUND, "考试不存在")

            users = []
            exam_depart_users = ExamDepart.objects.filter(exam=exam_profile)

            for exam_depart_user in exam_depart_users:
                # 首次考试
                if exam_depart_user.depart is not None:
                    depart = exam_depart_user.depart
                    user_departs = User.objects.filter(depart=depart)
                    for user_depart in user_departs:
                        users.append(user_depart)


                # 考试补考
                if exam_depart_user.user is not None:
                    user = exam_depart_user.user
                    users.append(user)

            # 获取考试参加人员
            users = list(set(users))

            data = []
            for user in users:
                result = Results.objects.filter(exam=exam_profile, user=user).first()
                if result is None:
                    data.append({
                        "username": user.username,
                        "name": user.name,
                        "status": 'absent',
                    })
                else:
                    result_questions = ResultQuestionSolution.objects.filter(results=result)
                    result_solution = 0
                    if not result_questions:
                        return api_response(ResponseCode.NO_CONTENT, "该答题卡没有记录")

                    else:
                        for result_question in result_questions:
                            if result_question.solution is not None:
                                result_solution += 1
                        response_rate = int(result_solution / len(result_questions)) * 100
                    data.append({
                        "username": user.username,
                        "name": user.name,
                        "status": 'attended',
                        "start_time": exam_profile.start_time,
                        "created_time": result.created_at,
                        "response_rate":response_rate
                    })

            serializer = UnExamUserSerializer(data, many=True)
            return api_response(ResponseCode.SUCCESS, "查询成功", serializer.data)
        except Exception as e:
            return api_response(ResponseCode.INTERNAL_SERVER_ERROR, f"服务器内部错误: {str(e)}")


